/*
 * Copyright (C) 2010-2012  Paulo Cesar Pereira de Andrade.
 *
 * This is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * Authors:
 *	Paulo Cesar Pereira de Andrade
 */

#include "exl.h"

/*
 * Types
 */
typedef union {
    struct {
#if __BYTE_ORDER == __LITTLE_ENDIAN
	ehalf_t		l;
	ehalf_t		h;
#else
	ehalf_t		h;
	ehalf_t		l;
#endif
    } h;
    eword_t		w;
} data_t;

/*
 * Implementation
 */
#if __WORDSIZE == 32
eint64_t
empz_get_ll(empz_t z)
{
    mp_size_t		n;
    mp_ptr		p;
    eint64_t		i;

    p = z->_mp_d;
    n = z->_mp_size;
    if (n > 0) {
	i = p[0];
	if (n > 1)
	    i |= (eint64_t)p[1] << 32;
    }
    else if (n < 0) {
	i = p[0];
	if (n < -1)
	    i |= (eint64_t)p[1] << 32;
	i = -i;
    }
    else
	i = 0;

    return (i);
}

void
empz_set_ll(empz_t z, eint64_t i)
{
    if (i != (eint32_t)i) {
	mpz_set_si(z, i >> 32);
	mpz_mul_2exp(z, z, 32);
	mpz_add_ui(z, z, i & 0xffffffff);
    }
    else
	mpz_set_si(z, i);
}

void
empz_set_ull(empz_t z, euint64_t u)
{
    if (u & 0xffffffff00000000LL) {
	mpz_set_ui(z, (euint64_t)u >> 32);
	mpz_mul_2exp(z, z, 32);
	mpz_add_ui(z, z, u & 0xffffffff);
    }
    else
	mpz_set_ui(z, u);
}
#endif

eint64_t
empq_get_ll(empq_t q)
{
    if (mpz_fits_slong_p(mpq_numref(q)) && mpz_fits_slong_p(mpq_denref(q)))
	return (mpz_get_si(mpq_numref(q)) / mpz_get_si(mpq_denref(q)));
    mpz_set_q(thr_zr, q);
    return (empz_get_ll(thr_zr));
}

eint64_t
empr_get_ll(empr_t r)
{
    if (unlikely(!mpfr_number_p(r)))
	ethread_kill(SIGFPE);
    if (mpfr_fits_slong_p(r, GMP_RNDZ))
	return (mpfr_get_si(r, GMP_RNDZ));
    mpfr_get_z(thr_zr, r, GMP_RNDZ);
    return (empz_get_ll(thr_zr));
}

eword_t
ew_mul_w_w(eword_t a, eword_t b)
{
    /* shameless cut&paste + adaptation/indent-style-change
     * from libgcc multiplication overflow check */
    const data_t uu = { .w = a };
    const data_t vv = { .w = b };

    if (likely(uu.h.h == uu.h.l >> HALFSHIFT)) {
	/* a fits in a single ehalf_t */
	if (likely(vv.h.h == vv.h.l >> HALFSHIFT)) {
	    /* b fits in a single ehalf_t as well */
	    /* a single multiplication, no overflow risk */
	    return ((eword_t)uu.h.l * (eword_t)vv.h.l);
	}
	else {
	    /* two multiplications */
	    data_t w0 = {
		.w = (euword_t)(euhalf_t)uu.h.l * (euword_t)(euhalf_t)vv.h.l
	    };
	    data_t w1 = {
		.w = (euword_t)(euhalf_t)uu.h.l * (euword_t)(euhalf_t)vv.h.h
	    };

	    if (vv.h.h < 0)
		w1.h.h -= uu.h.l;
	    if (uu.h.l < 0)
		w1.w -= vv.w;
	    w1.w += (euhalf_t)w0.h.h;
	    if (likely(w1.h.h == w1.h.l >> HALFSHIFT)) {
		w0.h.h = w1.h.l;
		return (w0.w);
	    }
	}
    }
    else if (likely(vv.h.h == vv.h.l >> HALFSHIFT)) {
	/* b fits into a single ehalf_t */
	/* two multiplications */
	data_t w0 = {
	    .w = (euword_t)(euhalf_t)uu.h.l * (euword_t)(euhalf_t)vv.h.l
	};
	data_t w1 = {
	    .w = (euword_t)(euhalf_t)uu.h.h * (euword_t)(euhalf_t)vv.h.l
	};

	if (uu.h.h < 0)
	    w1.h.h -= vv.h.l;
	if (vv.h.l < 0)
	    w1.w -= uu.w;
	w1.w += (euhalf_t)w0.h.h;
	if (likely(w1.h.h == w1.h.l >> 31)) {
	    w0.h.h = w1.h.l;
	    return (w0.w);
	}
    }
    /* A few sign checks and a single multiplication */
    else if (uu.h.h >= 0) {
	if (vv.h.h >= 0) {
	    if (uu.h.h == 0 && vv.h.h == 0) {
		const eword_t w = (euword_t)(euhalf_t)uu.h.l *
				  (euword_t)(euhalf_t)vv.h.l;

		if (likely(w >= 0))
		    return (w);
	    }
	}
	else if (uu.h.h == 0 && vv.h.h == (ehalf_t)-1) {
	    data_t w0 = {
		.w = (euword_t)(euhalf_t)uu.h.l * (euword_t)(euhalf_t)vv.h.l
	    };

	    w0.h.h -= uu.h.l;
	    if (likely(w0.h.h < 0))
		return (w0.w);
	}
    }
    else if (vv.h.h >= 0) {
	if (uu.h.h == (ehalf_t) -1 && vv.h.h == 0) {
	    data_t w0 = {
		.w = (euword_t)(euhalf_t) uu.h.l * (euword_t)(euhalf_t) vv.h.l
	    };

	    w0.h.h -= vv.h.l;
	    if (likely(w0.h.h < 0))
		return (w0.w);
	}
    }
    else if (uu.h.h == (ehalf_t)-1 && vv.h.h == (ehalf_t)-1) {
	data_t w0 = {
	    .w = (euword_t)(euhalf_t)uu.h.l * (euword_t)(euhalf_t) vv.h.l
	};

	w0.h.h -= uu.h.l;
	w0.h.h -= vv.h.l;
	if (likely(w0.h.h >= 0))
	    return (w0.w);
    }

    return (MININT);
}
